#include "menu.hpp"
#include "loader/sys.h"

#include <map>
#include <sys/stat.h>
#include <fstream>
#include <dirent.h>
#include <time.h>
#include <wchar.h>

#include "gecko.h"
#include "defines.h"

#include "networkops.h"

// Sounds
extern const u8 click_wav[];
extern const u32 click_wav_size;
extern const u8 hover_wav[];
extern const u32 hover_wav_size;
extern const u8 camera_wav[];
extern const u32 camera_wav_size;
// Pics
extern const u8 wait_png[];
extern const u8 btnplus_png[];
extern const u8 btnpluss_png[];
extern const u8 btnminus_png[];
extern const u8 btnminuss_png[];
extern const u8 background_png[];
extern const u8 butleft_png[];
extern const u8 butcenter_png[];
extern const u8 butright_png[];
extern const u8 butsleft_png[];
extern const u8 butscenter_png[];
extern const u8 butsright_png[];
extern const u8 pbarleft_png[];
extern const u8 pbarcenter_png[];
extern const u8 pbarright_png[];
extern const u8 pbarlefts_png[];
extern const u8 pbarcenters_png[];
extern const u8 pbarrights_png[];
// Fonts
extern const u8 butfont_ttf[];
extern const u32 butfont_ttf_size;
extern const u8 cffont_ttf[];
extern const u32 cffont_ttf_size;
const u8 *titlefont_ttf = butfont_ttf;
const u32 titlefont_ttf_size = butfont_ttf_size;
const u8 *lblfont_ttf = cffont_ttf;
const u32 lblfont_ttf_size = cffont_ttf_size;
const u8 *thxfont_ttf = cffont_ttf;
const u32 thxfont_ttf_size = cffont_ttf_size;

#define WIINNERTAG_URL "http://www.wiinnertag.com/wiinnertag_scripts/update_sign.php?key={KEY}&game_id={ID6}"

extern bool bootHB;

using namespace std;

CMenu::CMenu(CVideo &vid) :
	m_vid(vid)
{
	m_aa = 0;
	m_thrdWorking = false;
	m_thrdStop = false;
	m_thrdProgress = 0.f;
	m_thrdStep = 0.f;
	m_thrdStepLen = 0.f;
	m_locked = false;
	m_mutex = 0;
	m_showtimer = 0;
	m_exit = false;
	m_reload = false;
	bootHB = false;
}

extern "C" { int makedir(char *newdir); }

void CMenu::init()
{
	const char *drive = "sd";
	const char *check = "sd";
	struct stat dummy;

	for(int i = USB8; i >= SD; i--)
		if (DeviceHandler::Instance()->IsInserted(i) && stat(sfmt("%s:/%s/boot.dol", DeviceName[i], APPDATA_DIR2).c_str(), &dummy) == 0)
			drive = DeviceName[i];

	if(drive == check && !DeviceHandler::Instance()->IsInserted(SD))
	{
		_buildMenus();
		error(L"No boot.dol found on available partitions and no SD found!");
		m_exit = true;
		return;
	}

	m_appDir = sfmt("%s:/%s", drive, APPDATA_DIR2);
	gprintf("Wiiflow boot.dol Location: %s\n", m_appDir.c_str());

	m_cfg.load(sfmt("%s/" CFG_FILENAME, m_appDir.c_str()).c_str());

	bool onUSB = m_cfg.getBool("GENERAL", "data_on_usb", strncmp(drive, "usb", 3) == 0);
	if (onUSB)
		for(int i = USB8; i > SD; i--)
			if (DeviceHandler::Instance()->IsInserted(i) && stat(sfmt("%s:/%s", DeviceName[i], APPDATA_DIR).c_str(), &dummy) == 0)
				drive = DeviceName[i];
	else
		drive = DeviceHandler::Instance()->IsInserted(SD) ? DeviceName[SD] : drive;

	if(drive == check && !DeviceHandler::Instance()->IsInserted(SD))
	{
		_buildMenus();
		error(L"No boot.dol found on available partitions and no SD found!");
		m_exit = true;
		return;
	}

	m_dataDir = sfmt("%s:/%s", drive, APPDATA_DIR);
	gprintf("Data Directory: %s\n", m_dataDir.c_str());

	m_dol = sfmt("%s/boot.dol", m_appDir.c_str());
	m_ver = sfmt("%s/versions", m_appDir.c_str());
	m_app_update_zip = sfmt("%s/update.zip", m_appDir.c_str());
	m_data_update_zip = sfmt("%s/update.zip", m_dataDir.c_str());
	//
	m_cacheDir = m_cfg.getString("GENERAL", "dir_cache", sfmt("%s/cache", m_dataDir.c_str()));
	m_settingsDir = m_cfg.getString("GENERAL", "dir_settings", sfmt("%s/settings", m_dataDir.c_str()));
	m_languagesDir = m_cfg.getString("GENERAL", "dir_languages", sfmt("%s/languages", m_dataDir.c_str()));
	m_boxPicDir = m_cfg.getString("GENERAL", "dir_box_covers", sfmt("%s/boxcovers", m_dataDir.c_str()));
	m_picDir = m_cfg.getString("GENERAL", "dir_flat_covers", sfmt("%s/covers", m_dataDir.c_str()));
	m_themeDir = m_cfg.getString("GENERAL", "dir_themes", sfmt("%s/themes", m_dataDir.c_str()));
	m_musicDir = m_cfg.getString("GENERAL", "dir_music", sfmt("%s/music", m_dataDir.c_str())); 
	m_videoDir = m_cfg.getString("GENERAL", "dir_trailers", sfmt("%s/trailers", m_dataDir.c_str()));
	m_fanartDir = m_cfg.getString("GENERAL", "dir_fanart", sfmt("%s/fanart", m_dataDir.c_str()));
	m_screenshotDir = m_cfg.getString("GENERAL", "dir_screenshot", sfmt("%s/screenshots", m_dataDir.c_str()));
	m_txtCheatDir = m_cfg.getString("GENERAL", "dir_txtcheat", sfmt("%s/codes", m_dataDir.c_str()));
	m_altDolDir = m_cfg.getString("GENERAL", "dir_altdol", sfmt("%s/alt_dols", m_txtCheatDir.c_str()));
	m_bcaDir = m_cfg.getString("GENERAL", "dir_bca", sfmt("%s/bca", m_txtCheatDir.c_str()));
	m_cheatDir = m_cfg.getString("GENERAL", "dir_cheat", sfmt("%s/gct", m_txtCheatDir.c_str()));
	m_wdmDir = m_cfg.getString("GENERAL", "dir_wdm", sfmt("%s/wdm", m_txtCheatDir.c_str()));
	m_wipDir = m_cfg.getString("GENERAL", "dir_wip", sfmt("%s/wip", m_txtCheatDir.c_str()));
	//

	//Make important folders first.
	makedir((char *)m_cacheDir.c_str());
	makedir((char *)m_settingsDir.c_str());
	makedir((char *)m_languagesDir.c_str());
	makedir((char *)m_boxPicDir.c_str());
	makedir((char *)m_picDir.c_str());
	makedir((char *)m_themeDir.c_str());
	makedir((char *)m_musicDir.c_str());
	makedir((char *)m_videoDir.c_str());
	makedir((char *)m_fanartDir.c_str());
	makedir((char *)m_screenshotDir.c_str());
	makedir((char *)m_txtCheatDir.c_str());
	makedir((char *)m_altDolDir.c_str());
	makedir((char *)m_bcaDir.c_str());
	makedir((char *)m_cheatDir.c_str());
	makedir((char *)m_wdmDir.c_str());
	makedir((char *)m_wipDir.c_str());

	// INI files
	m_cat.load(sfmt("%s/" CAT_FILENAME, m_settingsDir.c_str()).c_str());
	string themeName = m_cfg.getString("GENERAL", "theme", "DEFAULT");
	m_themeDataDir = sfmt("%s/%s", m_themeDir.c_str(), themeName.c_str());

	u8 defaultMenuLanguage = 7; //English
	switch (CONF_GetLanguage())
	{
		case CONF_LANG_JAPANESE:
			defaultMenuLanguage = 14; //Japanese
			break;
		case CONF_LANG_GERMAN:
			defaultMenuLanguage = 11; //German
			break;
		case CONF_LANG_FRENCH:
			defaultMenuLanguage = 9; //French
			break;
		case CONF_LANG_SPANISH:
			defaultMenuLanguage = 19; //Spanish
			break;
		case CONF_LANG_ITALIAN:
			defaultMenuLanguage = 13; //Italian
			break;
		case CONF_LANG_DUTCH:
			defaultMenuLanguage = 6; //Dutch
			break;
		case CONF_LANG_SIMP_CHINESE:
			defaultMenuLanguage = 3; //Chinese_S
			break;
		case CONF_LANG_TRAD_CHINESE:
			defaultMenuLanguage = 4; //Chinese_T
			break;
		case CONF_LANG_KOREAN:
			defaultMenuLanguage = 7; // No Korean translation has been done for wiiflow, so the menu will use english in this case.
			break;
	}
	if (CONF_GetArea() == CONF_AREA_BRA)
		defaultMenuLanguage = 2; //Brazilian

	m_curLanguage = CMenu::_translations[m_cfg.getInt("GENERAL", "language", defaultMenuLanguage)];
	if (!m_loc.load(sfmt("%s/%s.ini", m_languagesDir.c_str(), m_curLanguage.c_str()).c_str()))
	{
		m_cfg.setInt("GENERAL", "language", 0);
		m_curLanguage = CMenu::_translations[0];
		m_loc.load(sfmt("%s/%s.ini", m_languagesDir.c_str(), m_curLanguage.c_str()).c_str());
	}

	m_aa = 3;

	CColor pShadowColor = CColor(0x3F000000);
	float pShadowX = 3.f;
	float pShadowY = 3.f;
	bool pShadowBlur = false;

	for(int chan = WPAD_MAX_WIIMOTES-1; chan >= 0; chan--)
	{
		m_cursor[chan].init(m_vid.wide(), pShadowColor, pShadowX, pShadowY, pShadowBlur, chan);
		WPAD_SetVRes(chan, m_vid.width() + m_cursor[chan].width(), m_vid.height() + m_cursor[chan].height());
	}
	m_btnMgr.init(m_vid);

	_load_installed_cioses();	

	_buildMenus();

	m_locked = m_cfg.getString("GENERAL", "parent_code", "").size() >= 4;
	m_btnMgr.setRumble(m_cfg.getBool("GENERAL", "rumble", true));

	int exit_to = m_cfg.getInt("GENERAL", "exit_to", 0);
	m_disable_exit = exit_to == 3;
	Sys_ExitTo(exit_to);

	LWP_MutexInit(&m_mutex, 0);

	m_btnMgr.setSoundVolume(m_cfg.getInt("GENERAL", "sound_volume_gui", 255));
}

int CMenu::main()
{
	InitNetworkThread();
	_config(1);

	GX_InvVtxCache();
	GX_InvalidateTexAll();
	m_cfg.save();
	if (m_reload)
	{
		_showWaitMessage();
		return 1;
	}
	return 0;
}

void CMenu::cleanup(void)
{
	_stopSounds();
	soundDeinit();
	LWP_MutexDestroy(m_mutex);
	m_mutex = 0;
	ShutdownNetworkThread();
	DeInit_Network();
}

void CMenu::_setAA(int aa)
{
	switch (aa)
	{
		case 2:
		case 3:
		case 4:
		case 5:
		case 6:
		case 8:
			m_aa = aa;
			break;
		case 7:
			m_aa = 6;
			break;
		default:
			m_aa = 0;
	}
}

void CMenu::_buildMenus(void)
{
	SThemeData theme;

	// Default fonts
	theme.btnFont.fromBuffer(butfont_ttf, butfont_ttf_size, 24, 24);
	theme.btnFontColor = CColor(0xD0BFDFFF);
	theme.lblFont.fromBuffer(lblfont_ttf, lblfont_ttf_size, 24, 24);
	theme.lblFontColor = CColor(0xD0BFDFFF);
	theme.txtFontColor = CColor(0xFFFFFFFF);
	theme.titleFont.fromBuffer(titlefont_ttf, titlefont_ttf_size, 36, 36);
	theme.titleFontColor = CColor(0xD0BFDFFF);
	theme.thxFont.fromBuffer(thxfont_ttf, thxfont_ttf_size, 18, 18);
	theme.thxFontColor = CColor(0xD0BFDFFF);
	// Default Sounds
	theme.clickSound.fromWAV(click_wav, click_wav_size);
	theme.hoverSound.fromWAV(hover_wav, hover_wav_size);
	theme.cameraSound.fromWAV(camera_wav, camera_wav_size);
	m_cameraSound = theme.cameraSound;
	// Default textures
	theme.btnTexL.fromPNG(butleft_png);
	theme.btnTexR.fromPNG(butright_png);
	theme.btnTexC.fromPNG(butcenter_png);
	theme.btnTexLS.fromPNG(butsleft_png);
	theme.btnTexRS.fromPNG(butsright_png);
	theme.btnTexCS.fromPNG(butscenter_png);
	theme.pbarTexL.fromPNG(pbarleft_png);
	theme.pbarTexR.fromPNG(pbarright_png);
	theme.pbarTexC.fromPNG(pbarcenter_png);
	theme.pbarTexLS.fromPNG(pbarlefts_png);
	theme.pbarTexRS.fromPNG(pbarrights_png);
	theme.pbarTexCS.fromPNG(pbarcenters_png);
	theme.btnTexPlus.fromPNG(btnplus_png);
	theme.btnTexPlusS.fromPNG(btnpluss_png);
	theme.btnTexMinus.fromPNG(btnminus_png);
	theme.btnTexMinusS.fromPNG(btnminuss_png);

	// Default background
	m_curBg.fromPNG(background_png, GX_TF_CMPR, ALLOC_MEM2, 64, 64);
	
	// Build menus
	_initErrorMenu(theme);
	_initConfigAdvMenu(theme);
	_initConfigSndMenu(theme);
	_initConfig7Menu(theme);
	_initConfig4Menu(theme);
	_initConfigScreenMenu(theme);
	_initConfig3Menu(theme);
	_initConfigMenu(theme);
	_initDownloadMenu(theme);
	_initCodeMenu(theme);
	_initAboutMenu(theme);
	_initSystemMenu(theme);
}

u32 CMenu::_addButton(CMenu::SThemeData &theme, const char *, SFont font, const wstringEx &text, int x, int y, u32 width, u32 height, const CColor &color)
{
	SButtonTextureSet btnTexSet;
	CColor c(color);
	btnTexSet.left = theme.btnTexL;
	btnTexSet.right = theme.btnTexR;
	btnTexSet.center = theme.btnTexC;
	btnTexSet.leftSel = theme.btnTexLS;
	btnTexSet.rightSel = theme.btnTexRS;
	btnTexSet.centerSel = theme.btnTexCS;
	SSoundEffect clickSound = theme.clickSound;
	SSoundEffect hoverSound = theme.hoverSound;
	return m_btnMgr.addButton(font, text, x, y, width, height, c, btnTexSet, clickSound, hoverSound);
}

u32 CMenu::_addPicButton(CMenu::SThemeData &theme, const char *, STexture &texNormal, STexture &texSelected, int x, int y, u32 width, u32 height)
{
	STexture tex1 = texNormal;
	STexture tex2 = texSelected;
	SSoundEffect clickSound = theme.clickSound;
	SSoundEffect hoverSound = theme.hoverSound;
	return m_btnMgr.addPicButton(tex1, tex2, x, y, width, height, clickSound, hoverSound);
}

u32 CMenu::_addLabel(CMenu::SThemeData, const char *, SFont font, const wstringEx &text, int x, int y, u32 width, u32 height, const CColor &color, u16 style)
{
	CColor c(color);
	return m_btnMgr.addLabel(font, text, x, y, width, height, c, style);
}

u32 CMenu::_addLabel(CMenu::SThemeData, const char *, SFont font, const wstringEx &text, int x, int y, u32 width, u32 height, const CColor &color, u16 style, STexture &bg)
{
	CColor c(color);
	STexture texBg = bg;
	return m_btnMgr.addLabel(font, text, x, y, width, height, c, style, texBg);
}

u32 CMenu::_addProgressBar(CMenu::SThemeData &theme, const char *, int x, int y, u32 width, u32 height)
{
	SButtonTextureSet btnTexSet;
	btnTexSet.left = theme.pbarTexL;
	btnTexSet.right = theme.pbarTexR;
	btnTexSet.center = theme.pbarTexC;
	btnTexSet.leftSel = theme.pbarTexLS;
	btnTexSet.rightSel = theme.pbarTexRS;
	btnTexSet.centerSel = theme.pbarTexCS;
	return m_btnMgr.addProgressBar(x, y, width, height, btnTexSet);
}

void CMenu::_setHideAnim(u32 id, const char *, int dx, int dy, float scaleX, float scaleY)
{
	m_btnMgr.hide(id, dx, dy, scaleX, scaleY, true);
}

void CMenu::_mainLoopCommon(bool blockExit)
{
	ScanInput();

	m_btnMgr.tick();
	
	m_vid.prepare();
	m_vid.setup2DProjection();
	_drawBg();
	m_btnMgr.draw();

	m_vid.render();

	if(!blockExit)
	{
		if (Sys_Exiting())
		{
			m_cat.save();
			m_cfg.save();
		}
		Sys_Test();
	}
	
	//Take Screenshot
	if (gc_btnsPressed & PAD_TRIGGER_Z)
	{
		time_t rawtime;
		struct tm * timeinfo;
		char buffer[80];

		time(&rawtime);
		timeinfo = localtime(&rawtime);
		strftime(buffer,80,"%b-%d-20%y-%Hh%Mm%Ss.png",timeinfo);
		gprintf("Screenshot taken and saved to: %s/%s\n", m_screenshotDir.c_str(), buffer);
		m_vid.TakeScreenshot(sfmt("%s/%s", m_screenshotDir.c_str(), buffer).c_str());
		m_cameraSound.play(255);
	}
}

void CMenu::_drawBg(void)
{
	Mtx modelViewMtx;
	GXTexObj texObj;

	GX_ClearVtxDesc();
	GX_SetNumTevStages(1);
	GX_SetNumChans(0);
	GX_SetVtxDesc(GX_VA_POS, GX_DIRECT);
	GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_POS, GX_POS_XYZ, GX_F32, 0);
	GX_SetVtxDesc(GX_VA_TEX0, GX_DIRECT);
	GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_TEX0, GX_TEX_ST, GX_F32, 0);
	GX_SetNumTexGens(1);
	GX_SetTevColorIn(GX_TEVSTAGE0, GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO, GX_CC_TEXC);
	GX_SetTevAlphaIn(GX_TEVSTAGE0, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO);
	GX_SetTevColorOp(GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_TRUE, GX_TEVPREV);
	GX_SetTevAlphaOp(GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_TRUE, GX_TEVPREV);
	GX_SetTexCoordGen(GX_TEXCOORD0, GX_TG_MTX2x4, GX_TG_TEX0, GX_IDENTITY);
	GX_SetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP0, GX_COLORNULL);
	GX_SetBlendMode(GX_BM_NONE, GX_BL_SRCALPHA, GX_BL_INVSRCALPHA, GX_LO_CLEAR);
	GX_SetAlphaUpdate(GX_FALSE);
	GX_SetCullMode(GX_CULL_NONE);
	GX_SetZMode(GX_DISABLE, GX_ALWAYS, GX_FALSE);
	guMtxIdentity(modelViewMtx);
	GX_LoadPosMtxImm(modelViewMtx, GX_PNMTX0);
	GX_InitTexObj(&texObj, m_curBg.data.get(), m_curBg.width, m_curBg.height, m_curBg.format, GX_CLAMP, GX_CLAMP, GX_FALSE);
	GX_LoadTexObj(&texObj, GX_TEXMAP0);
	GX_Begin(GX_QUADS, GX_VTXFMT0, 4);
	GX_Position3f32(0.f, 0.f, 0.f);
	GX_TexCoord2f32(0.f, 0.f);
	GX_Position3f32(640.f, 0.f, 0.f);
	GX_TexCoord2f32(1.f, 0.f);
	GX_Position3f32(640.f, 480.f, 0.f);
	GX_TexCoord2f32(1.f, 1.f);
	GX_Position3f32(0.f, 480.f, 0.f);
	GX_TexCoord2f32(0.f, 1.f);
	GX_End();
}

void CMenu::_updateText(void)
{
	_textError();
	_textConfig();
	_textConfig3();
	_textConfigScreen();
	_textConfig4();
	_textConfig7();
	_textConfigSnd();
	_textConfigAdv();
	_textDownload();
	_textCode();
	_textSystem();
}

const wstringEx CMenu::_fmt(const char *key, const wchar_t *def)
{
	wstringEx ws = m_loc.getWString(m_curLanguage, key, def);
	if (checkFmt(def, ws)) return ws;
	return def;
}

void CMenu::_stopSounds(void)
{
	m_btnMgr.stopSounds();
}

void CMenu::_load_installed_cioses()
{
	if (_installed_cios.size() > 0) return;
	
	// Do sjizzle
	u32 count;
	u32 ret = ES_GetNumTitles(&count);
	if (ret || !count)
	{
		gprintf("Cannot count...aaaah\n");
		return;
	}

	static u64 title_list[256] ATTRIBUTE_ALIGN(32);
	if (ES_GetTitles(title_list, count) > 0)
	{
		gprintf("Cannot get titles...\n");
		return;
	}
	
	for (u32 i = 0; i < count; i++)
	{
		u32 tmd_size;
		if (ES_GetStoredTMDSize(title_list[i], &tmd_size) > 0) continue;
		static u8 tmd_buf[MAX_SIGNED_TMD_SIZE] ATTRIBUTE_ALIGN(32);
		signed_blob *s_tmd = (signed_blob *) tmd_buf;
		if (ES_GetStoredTMD(title_list[i], s_tmd, tmd_size) > 0) continue;

		const tmd *t = (const tmd *) SIGNATURE_PAYLOAD(s_tmd);
		
		u32 kind = t->title_id >> 32;

		if (kind == 1)
		{
			u32 title_l = t->title_id & 0xFFFFFFFF;
			if (title_l == 0 || title_l == 2 || title_l == 0x100 || title_l == 0x101) continue;
			
			// We have an ios
			
			u32 version = t->title_version;
			if (tmd_buf[4] == 0 && (version < 100 || version == 0xFFFF)) // Signature is empty
			{
				// Probably an cios
				SIOS s;
				s.ios = title_l;
				s.ar_index = get_ios_info(s_tmd, tmd_size);
				_installed_cios.push_back(s);
			}
		}
	}
	
	sort(_installed_cios.begin(), _installed_cios.end(), CMenu::_sortByIOS);
}

bool CMenu::_sortByIOS(SIOS item1, SIOS item2)
{
	return item1.ios > item2.ios;
}

void CMenu::_hideWaitMessage()
{
	m_vid.hideWaitMessage();
}

void CMenu::_showWaitMessage()
{
	m_vid.waitMessage(0.2f);
}